﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using Combinatorics.Collections;
using SQLite;
using System.Runtime.InteropServices;
using Equin.ApplicationFramework;


namespace ReAlchemy
{
    public partial class MainForm : Form
    {
        // public SQLiteConnection db = null;

        IngredientIn ingrForm = new IngredientIn();

        SplashScreen splashScreen = new SplashScreen();

        List<Ingredient> ingrList = null;

        List<Recipe> recipeList = null;

        AutoCompleteStringCollection asEffectStrings = new AutoCompleteStringCollection();
        AutoCompleteStringCollection asIngredientStrings = new AutoCompleteStringCollection();


        BindingListView<Recipe> recipeView = null;

        bool ingrListChanged = true;

        public MainForm()
        {
            InitializeComponent();
        }

        private void label1_Click(object sender, EventArgs e)
        {

        }

        private void btSetIngredients_Click(object sender, EventArgs e)
        {
            // IngredientIn ingrForm = new IngredientIn();
            ingrForm.ResetList();
            if (ingrForm.ShowDialog(this) == DialogResult.OK)
                updatedIngrList();
            // ingrForm.Activate();
        }

        private void setIngredientString()
        {
            // TableQuery<Ingredient> Ingredients = MainDB.db.Table<Ingredient>();

            int AllIngr = MainDB.tblIngredient.Count();
            int CheckedIngr = MainDB.tblIngredient.Where(ingr => ingr.Checked == 1).Count();

            if (CheckedIngr == 0) lblICount.Text = "None";
            else if (CheckedIngr == AllIngr) lblICount.Text = "All";
            else lblICount.Text = String.Format("{0}", CheckedIngr);
        }

        public void updatedIngrList() {
            setIngredientString();            
            ingrList = MainDB.tblIngredient.Where(ingr => ingr.Checked == 1).Cast<Ingredient>().ToList();
            ingrListChanged = true;
            asIngredientStrings.Clear();
            foreach (Ingredient ingr in ingrList) {
                asIngredientStrings.Add(ingr.Name);
            }
            listNeedsUpdate();
        }

        private void listNeedsUpdate() {
            btFill.Font = new Font(btFill.Font, FontStyle.Bold);
        }

        private void MainForm_Shown(object sender, EventArgs e)
        {
            // db = new SQLiteConnection("ingdata.db");
            MainDB.openDB();
            updatedIngrList();

            foreach (EffectName eff in MainDB.tblEffectName) {
                asEffectStrings.Add(eff.ReqName);
            }

            tboxEffectFilter.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            tboxEffectFilter.AutoCompleteSource = AutoCompleteSource.CustomSource;
            tboxEffectFilter.AutoCompleteCustomSource = asEffectStrings;

            tboxIngrFilter.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            tboxIngrFilter.AutoCompleteSource = AutoCompleteSource.CustomSource;
            tboxIngrFilter.AutoCompleteCustomSource = asIngredientStrings;

            cboxEffKindFilter.SelectedIndex = 0;
            cboxFIngrCount.SelectedIndex = 0;
            cboxFEffCOp.SelectedIndex = 0;
            cboxFValueOp.SelectedIndex = 0;


            gridRecipes.RowPostPaint += OnRowPostPaint;
            setPlayerToFields();

            setFiltersHidden();
        }

        private void setPlayerToFields() {
            tboxPAlchLevel.Text = MainDB.player.AlchemyLevel.ToString();
            tboxPFortifyAlch.Text = MainDB.player.FortifyAlchemy.ToString();
            cboxPLoreLevel.SelectedIndex = cboxPLoreLevel.Items.IndexOf(MainDB.player.AlchemicalLoreLevel.ToString());
            // MainDB.player.ImprovedElixirs;
            // MainDB.player.ImprovedPoisons;
            // MainDB.player.Purification;
            cbPImpElixirs.Checked = MainDB.player.ImprovedElixirs > 0;
            cbPImpPoisons.Checked = MainDB.player.ImprovedPoisons > 0;
            cbPPurification.Checked = MainDB.player.Purification > 0;
               
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            MainDB.closeDB();
        }

        private void getRecipes() { 
            // recipeList = new List<Recipe>();
            lblStatus.Text = "Calculating recipe combinations...";
            lblStatus.Update();
            BindingSource recipeSource = new BindingSource();
            if (ingrListChanged)
            {
                recipeList = new List<Recipe>();
                progCalc.Value = 0;
                progCalc.Visible = true;
                int ic = ingrList.Count;
                int maxit = (int)((ic * (ic - 1) / 2) + (ic * (ic - 1) * (ic - 2) / 6));
                int curri = 0;
                progCalc.Maximum = maxit;
                var combs = new Combinations<Ingredient>(ingrList, 2);
                foreach (List<Ingredient> comb in combs)
                {
                    Recipe r = new Recipe(comb);
                    if (!r.IsDud())
                        recipeList.Add(r);
                    progCalc.Value = ++curri;
                }
                // Now 3 Ingredient combinations:
                combs = new Combinations<Ingredient>(ingrList, 3);
                foreach (List<Ingredient> comb in combs)
                {
                    Recipe r = new Recipe(comb);
                    if (!r.IsDud())
                        recipeList.Add(r);
                    progCalc.Value = ++curri;
                }
                Cursor.Current = Cursors.WaitCursor;
                splashScreen.Show();
                splashScreen.Update();
               
                lblStatus.Text = String.Format("Making list of {0} items sortable and filtrable... Please be patient...", recipeList.Count);
                lblStatus.Update();
                progCalc.Update();
                recipeView = new BindingListView<Recipe>(recipeList);
                progCalc.Visible = false;
                splashScreen.Hide();
            }
            else {
                lblStatus.Text = "Recalculating recipes ...";
                lblStatus.Update();
                splashScreen.Show();
                splashScreen.Update();

                progCalc.Visible = true;
                progCalc.Maximum = recipeList.Count();
                int it = 0;
                foreach (Recipe recipe in recipeList) { 
                    recipe.ReMixPotion();
                    progCalc.Value = it;
                }
                progCalc.Visible = false;
                splashScreen.Hide();
            }

            btnApplyFilters.PerformClick();

            lblStatus.Text = String.Format("Displaying {0} Items", recipeView.Count);

            gridRecipes.Update();
            ingrListChanged = false;

            Cursor.Current = Cursors.Default;
            lblStatus.Update();
            recipeSource.DataSource = recipeView;
            gridRecipes.Columns.Clear();
            gridRecipes.DataSource = recipeSource;
            gridRecipes.Columns["Name"].Width = 150;
            gridRecipes.Columns["IngredientStr"].HeaderText = "Ingredients";
            gridRecipes.Columns["IngredientStr"].Width = 180;
            gridRecipes.Columns["IngredientCount"].HeaderText = "I#";
            gridRecipes.Columns["IngredientCount"].ToolTipText = "Ingredient Count";
            gridRecipes.Columns["IngredientCount"].Width = 28;
            gridRecipes.Columns["EffectStr"].HeaderText = "Effects";
            gridRecipes.Columns["EffectStr"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            gridRecipes.Columns["EffectCount"].HeaderText = "E#";
            gridRecipes.Columns["EffectCount"].ToolTipText = "Effect Count";
            gridRecipes.Columns["EffectCount"].Width = 28;
            gridRecipes.Columns["Value"].Width = 56;
            recipeSource.Sort = "Value DESC";
        }

        private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {

        }

        private void btFill_Click(object sender, EventArgs e)
        {
            getRecipes();
            btFill.Font = new Font(btFill.Font, FontStyle.Regular);
            btnApplyFilters.Enabled = true;
        }

        private void gridRecipes_SelectionChanged(object sender, EventArgs e)
        {
            // if (gridRecipes.SelectedRows.Count == 1)
            
            foreach (DataGridViewRow row in gridRecipes.SelectedRows) {
                // customersBindingSource.Current).Object
                Recipe rec = ((ObjectView<Recipe>)row.DataBoundItem).Object;

                lblPotionName.Text = rec.Name;
                if (rec.IsPoison())
                    lblPotionName.ForeColor = Color.Red;
                else
                    lblPotionName.ForeColor = Color.Green;

                lblValue.Text = rec.Value.ToString();
                rtbIngredients.Clear();
                rtbIngredients.AppendText(String.Join(Environment.NewLine, rec.Ingredients), Color.Blue, FontStyle.Bold);
                rtbEffects.Clear();
                foreach (EffectPair effp in rec.Effects)
                {
                    if (rtbEffects.Text != "") rtbEffects.AppendText(Environment.NewLine);
                    if (effp.first.Base.Flags.hostile)
                        rtbEffects.AppendText(effp.first.Base.ReqName, Color.Red, FontStyle.Bold);
                    else
                        rtbEffects.AppendText(effp.first.Base.ReqName, Color.Green, FontStyle.Bold);
                    rtbEffects.AppendText(" - " + effp.pow.getDescription(), Color.Black, FontStyle.Regular);
                }
                break;
            } 
             
        }

        void OnRowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            Recipe rec = ((ObjectView<Recipe>)gridRecipes.Rows[e.RowIndex].DataBoundItem).Object;
            DataGridViewCellStyle style = gridRecipes.Rows[e.RowIndex].DefaultCellStyle;
            if (!object.ReferenceEquals(null, rec)) {

                // Do whatever you want with style and value
                if (rec.IsPoison())
                    style.BackColor = Color.LightPink;
                else 
                    style.BackColor = Color.LightGreen;

            }
        }

        private void gridRecipes_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {

        }

        private void btnApplyFilters_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            lblStatus.Text = "Applying filters ...";
            lblStatus.Update();
            splashScreen.Show();
            splashScreen.Update();
            if (tboxNameFilter.Text != "" || lboxIngrFilters.Items.Count > 0 || lboxEffFilters.Items.Count > 0 || cboxEffKindFilter.SelectedIndex != 0 || cboxFIngrCount.SelectedIndex != 0 
                || cboxFEffCOp.SelectedIndex != 0 || cboxFValueOp.SelectedIndex != 0)
            {
                recipeView.ApplyFilter(new RecipeFilter(tboxNameFilter.Text, lboxIngrFilters.Items, lboxEffFilters.Items, cboxEffKindFilter.SelectedIndex, cboxFIngrCount.SelectedIndex, 
                                                        cboxFEffCOp.SelectedIndex, int.Parse(tboxFEffCVal.Text), cboxFValueOp.SelectedIndex, int.Parse(tboxFValue.Text)));
                btnClearFilters.Enabled = true;
            }
            else { 
                recipeView.RemoveFilter();
                btnClearFilters.Enabled = false;
            }

            if (tboxNameFilter.Text != "") lblNameFilter.ForeColor = Color.Blue;
            if (lboxIngrFilters.Items.Count > 0) lblIngrFilter.ForeColor = Color.Blue;
            if (lboxEffFilters.Items.Count > 0) lblEffectFilter.ForeColor = Color.Blue;
            if (cboxEffKindFilter.SelectedIndex != 0) lblEffKindFilter.ForeColor = Color.Blue;
            if (cboxFIngrCount.SelectedIndex != 0) lblFIngrCount.ForeColor = Color.Blue;
            if (cboxFEffCOp.SelectedIndex != 0) lblFEffCount.ForeColor = Color.Blue;
            if (cboxFValueOp.SelectedIndex != 0) lblFValue.ForeColor = Color.Blue;

            Cursor.Current = Cursors.Default;
            splashScreen.Hide();
            lblStatus.Text = String.Format("Displaying {0} Items", recipeView.Count);

        }

        private void tboxNameFilter_TextChanged(object sender, EventArgs e)
        {
            if (tboxNameFilter.Text == "") lblNameFilter.ForeColor = Color.Black;
            else lblNameFilter.ForeColor = Color.Red;
        }

        private void tboxIngrFilter_TextChanged(object sender, EventArgs e)
        {
            // if (tboxIngrFilter.Text == "") lblIngrFilter.ForeColor = Color.Black;
            // else lblIngrFilter.ForeColor = Color.Red;
        }

        private void tboxEffectFilter_TextChanged(object sender, EventArgs e)
        {
            // if (tboxEffectFilter.Text == "") lblEffectFilter.ForeColor = Color.Black;
            // else lblEffectFilter.ForeColor = Color.Red;
        }

        private void cboxEffKindFilter_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cboxEffKindFilter.SelectedIndex == 0) lblEffKindFilter.ForeColor = Color.Black;
            else lblEffKindFilter.ForeColor = Color.Red;
        }



        private void tboxPAlchLevel_Validating(object sender, CancelEventArgs e)
        {
            if (tboxPAlchLevel.Text == "")
                tboxPAlchLevel.Text = "0";           
            int num;
            if (!int.TryParse(tboxPAlchLevel.Text, out num))
            {
                e.Cancel = true;
                tboxPAlchLevel.SelectAll();
            }
        }

        private void tboxPAlchLevel_Validated(object sender, EventArgs e)
        {
            MainDB.player.AlchemyLevel = int.Parse(tboxPAlchLevel.Text);
        }

        private void tboxPFortifyAlch_Validating(object sender, CancelEventArgs e)
        {
            if (tboxPFortifyAlch.Text == "")
                tboxPFortifyAlch.Text = "0";
            int num;
            if (!int.TryParse(tboxPFortifyAlch.Text, out num))
            {
                e.Cancel = true;
                tboxPFortifyAlch.SelectAll();
            }
        }

        private void tboxPFortifyAlch_Validated(object sender, EventArgs e)
        {
            MainDB.player.FortifyAlchemy = int.Parse(tboxPFortifyAlch.Text);
        }

        private void cboxPLoreLavel_SelectedValueChanged(object sender, EventArgs e)
        {
            MainDB.player.AlchemicalLoreLevel = int.Parse(this.cboxPLoreLevel.Text);
            listNeedsUpdate();
        }

        private void cbPImpElixirs_CheckedChanged(object sender, EventArgs e)
        {
            MainDB.player.ImprovedElixirs = cbPImpElixirs.Checked ? 1 : 0;
            listNeedsUpdate();
        }

        private void cbPImpPoisons_CheckedChanged(object sender, EventArgs e)
        {
            MainDB.player.ImprovedPoisons = cbPImpPoisons.Checked ? 1 : 0;
            listNeedsUpdate();
        }

        private void cbPPurification_CheckedChanged(object sender, EventArgs e)
        {
            MainDB.player.Purification = cbPPurification.Checked ? 1 : 0;
            listNeedsUpdate();
        }

        private void tboxPAlchLevel_TextChanged(object sender, EventArgs e)
        {
            listNeedsUpdate();
        }

        private void tboxPFortifyAlch_TextChanged(object sender, EventArgs e)
        {
            listNeedsUpdate();
        }

        private void btnClearFilters_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            lblStatus.Text = "Removing filters ...";
            lblStatus.Update();
            splashScreen.Show();
            splashScreen.Update();

            tboxNameFilter.Text = "";
            tboxIngrFilter.Text = "";
            btnFClrIngr.PerformClick();
            tboxEffectFilter.Text = "";
            btnFClrEff.PerformClick();
            cboxEffKindFilter.SelectedIndex = 0;
            cboxFIngrCount.SelectedIndex = 0;
            cboxFEffCOp.SelectedIndex = 0;
            recipeView.RemoveFilter();

            Cursor.Current = Cursors.Default;
            splashScreen.Hide();
            lblStatus.Text = String.Format("Displaying {0} Items", recipeView.Count);

            // Not sure if I need any of those:
            lblNameFilter.ForeColor = Color.Black;
            lblIngrFilter.ForeColor = Color.Black;
            lblEffectFilter.ForeColor = Color.Black;
            lblEffKindFilter.ForeColor = Color.Black;
            lblFIngrCount.ForeColor = Color.Black;
            lblFEffCount.ForeColor = Color.Black;
            lblFValue.ForeColor = Color.Black;

            btnClearFilters.Enabled = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void OnFilterBoxEnter(object sender, EventArgs e)
        {
            AcceptButton = btnApplyFilters;
        }

        private void OnFilterBoxLeave(object sender, EventArgs e)
        {
            AcceptButton = null;
        }

        private void OnPlayerBoxEnter(object sender, EventArgs e)
        {
            AcceptButton = btFill;
        }

        private void OnPlayerBoxLeave(object sender, EventArgs e)
        {
            AcceptButton = null;
        }

        private void OnFilterIngrEnter(object sender, EventArgs e)
        {
            AcceptButton = btnFAddIngr;
        }

        private void btnFAddIngr_Click(object sender, EventArgs e)
        {
            if (tboxIngrFilter.Text != "")  {
                // Search for duplicates
                foreach (string s in lboxIngrFilters.Items)
                    if (s.Substring(1, s.Length-1).ToLower() == tboxIngrFilter.Text.Trim().ToLower())
                        return;
                int idx = 0;
                if (object.ReferenceEquals(sender, btnFAddIngr))
                    idx = lboxIngrFilters.Items.Add("+"+tboxIngrFilter.Text.Trim());
                else if (object.ReferenceEquals(sender, btnFAddIngrE))
                    idx = lboxIngrFilters.Items.Add("-"+tboxIngrFilter.Text.Trim());
                lboxIngrFilters.SelectedIndex = idx;
                lboxIngrFilters.Focus();
                lblIngrFilter.ForeColor = Color.Red;
            }
        }

        private void btnFRemIngr_Click(object sender, EventArgs e)
        {
            if (lboxIngrFilters.SelectedIndex >= 0)
            {
                int idx = lboxIngrFilters.SelectedIndex;
                lboxIngrFilters.Items.RemoveAt(lboxIngrFilters.SelectedIndex);
                if (lboxIngrFilters.Items.Count <= idx)
                    idx = lboxIngrFilters.Items.Count - 1;
                if (lboxIngrFilters.Items.Count > 0)
                {
                    lboxIngrFilters.SelectedIndex = idx;
                }
                else lblIngrFilter.ForeColor = Color.Black;
                //else
                  //  lblIngrFilter.ForeColor = Color.Blue;
            }
        }

        private void btnFClrIngr_Click(object sender, EventArgs e)
        {
            lboxIngrFilters.Items.Clear();
            lblIngrFilter.ForeColor = Color.Black;
        }

        private void lboxIngrFilters_KeyPress(object sender, KeyPressEventArgs e)
        {

        }

        private void lboxIngrFilters_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete) {
                btnFRemIngr.PerformClick();                    
            }
        }

        private void panel2_Paint(object sender, PaintEventArgs e)
        {

        }

        private void MainForm_Load(object sender, EventArgs e)
        {

        }

        private void setFiltersHidden()
        {
            lblFilterSH.Text = "Show Filters";
            lblFilterSH.ImageIndex = 1;
            splitContainer1.Panel1Collapsed = true;
        }

        private void setFiltersVisible() {
            lblFilterSH.Text = "Hide Filters";
            lblFilterSH.ImageIndex = 0;
            splitContainer1.Panel1Collapsed = false;
        }

        private void toggleFiltersVisible() {
            if (splitContainer1.Panel1Collapsed)
            {
                setFiltersVisible();
            }
            else
            {
                setFiltersHidden();
            }
        }

        private void lblFilterSH_Click(object sender, EventArgs e)
        {
            toggleFiltersVisible();
        }

        private void btnFAddEff_Click(object sender, EventArgs e)
        {
            if (tboxEffectFilter.Text != "")
            {
                // Search for duplicates
                foreach (string s in lboxEffFilters.Items)
                    if (s.Substring(1, s.Length - 1).ToLower() == tboxEffectFilter.Text.Trim().ToLower())
                        return;
                int idx = 0;
                if (object.ReferenceEquals(sender, btnFAddEff))
                    idx = lboxEffFilters.Items.Add("+" + tboxEffectFilter.Text.Trim());
                else if (object.ReferenceEquals(sender, btnFAddEffE))
                    idx = lboxEffFilters.Items.Add("-" + tboxEffectFilter.Text.Trim());
                lboxEffFilters.SelectedIndex = idx;
                lboxEffFilters.Focus();
                lblEffectFilter.ForeColor = Color.Red;
            }
        }

        private void btnFClrEff_Click(object sender, EventArgs e)
        {
            lboxEffFilters.Items.Clear();
            lblEffectFilter.ForeColor = Color.Black;
        }

        private void btnFRemEff_Click(object sender, EventArgs e)
        {
            if (lboxEffFilters.SelectedIndex >= 0)
            {
                int idx = lboxEffFilters.SelectedIndex;
                lboxEffFilters.Items.RemoveAt(lboxEffFilters.SelectedIndex);
                if (lboxEffFilters.Items.Count <= idx)
                    idx = lboxEffFilters.Items.Count - 1;
                if (lboxEffFilters.Items.Count > 0)
                {
                    lboxEffFilters.SelectedIndex = idx;
                }
                else lblEffectFilter.ForeColor = Color.Black;
                //else
                //  lblIngrFilter.ForeColor = Color.Blue;
            }

        }

        private void tboxEffectFilter_Enter(object sender, EventArgs e)
        {
            AcceptButton = btnFAddEff;

        }

        private void tboxEffectFilter_Leave(object sender, EventArgs e)
        {
            AcceptButton = null;
        }

        private void OnFilterIngrLeave(object sender, EventArgs e)
        {
            AcceptButton = null;
        }

        private void lboxEffFilters_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                btnFRemEff.PerformClick();
            }
        }

        private void cboxFIngrCount_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cboxFIngrCount.SelectedIndex == 0) lblFIngrCount.ForeColor = Color.Black;
            else lblFIngrCount.ForeColor = Color.Red;
        }

        private void cboxFEffCOp_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cboxFEffCOp.SelectedIndex > 0)
                lblFEffCount.ForeColor = Color.Red;
            else
                lblFEffCount.ForeColor = Color.Black;
        }

        private void tboxFEffCVal_Validating(object sender, CancelEventArgs e)
        {
            int num;
            if (tboxFEffCVal.Text == "") tboxFEffCVal.Text = "1";
            if (!int.TryParse(tboxFEffCVal.Text, out num))
            {
                e.Cancel = true;
                tboxFEffCVal.SelectAll();
            }
            else { // Value is inte
                // Limit values to 1 - 6
                if (num < 1)
                    tboxFEffCVal.Text = "1";
                if (num > 6)
                    tboxFEffCVal.Text = "6";
            }
        }

        private void cboxFValueOp_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cboxFValueOp.SelectedIndex > 0)
                lblFValue.ForeColor = Color.Red;
            else
                lblFValue.ForeColor = Color.Black;

        }

        private void tboxFValue_Validating(object sender, CancelEventArgs e)
        {
            int num;
            if (tboxFValue.Text == "") tboxFValue.Text = "0";
            if (!int.TryParse(tboxFValue.Text, out num))
            {
                e.Cancel = true;
                tboxFValue.SelectAll();
            }
            else
            { // Value is integer
                // Limit to positive integers only
                if (num < 0)
                    tboxFValue.Text = "0";
            }

        }

        private void tboxFEffCVal_TextChanged(object sender, EventArgs e)
        {
            if (cboxFEffCOp.SelectedIndex > 0)
                lblFIngrCount.ForeColor = Color.Red;
        }

        private void tboxFValue_TextChanged(object sender, EventArgs e)
        {
            if (cboxFValueOp.SelectedIndex > 0)
                lblFValue.ForeColor = Color.Red;

        }

    }

    public class RecipeFilter : IItemFilter<Recipe>
    {
        private string fname = "";
        ListBox.ObjectCollection lingr = null;
        ListBox.ObjectCollection leffects = null;
        private int feffkind = 0;
        private int fingcount = 0;
        private int feffcountop = 0;
        private int feffcountval = 0;
        private int fvalueop = 0;
        private int fvalue = 0;
        public RecipeFilter(string sName, ListBox.ObjectCollection lIngredients, ListBox.ObjectCollection lEffects, int iEffKind, int iIngrCount, int iEffCountOp,  int iEffCountVal, int iValueOp, int iValue)
        {
            fname = sName.ToLower();
            lingr = lIngredients;
            leffects = lEffects;
            feffkind = iEffKind;
            fingcount = iIngrCount;
            feffcountop = iEffCountOp;
            feffcountval = iEffCountVal;
            fvalueop = iValueOp;
            fvalue = iValue;
        }

        public bool Include(Recipe item)
        {
            if (fname != "" && !item.Name.ToLower().Contains(fname)) return false;
            // if (fingr != "" && !item.IngredientStr.ToLower().Contains(fingr)) return false;
            if (lingr != null && lingr.Count > 0)
            {
                foreach (string singr in lingr) {
                    string fingr = singr.Substring(1, singr.Length - 1);
                    bool exclude = singr.Substring(0, 1) == "-";
                    
                    bool found = false;
                    foreach (Ingredient ingr in item.Ingredients) {
                        if (ingr.Name.ToLower() == fingr.ToLower().Trim())
                        {
                            found = true;
                            break;
                        }
                    }
                    if (exclude && found || !exclude && !found) return false;
                }
            }

            if (leffects != null && leffects.Count > 0)
            {
                foreach (string seff in leffects)
                {
                    string feffect = seff.Substring(1, seff.Length - 1).ToLower().Trim();
                    bool exclude = seff.Substring(0, 1) == "-";
                    bool found = false;
                    foreach (EffectPair effp in item.Effects)
                    {
                        if (effp.first.Base.ReqName.ToLower() == feffect)
                        {
                            found = true;
                            break;
                        }
                    }
                    if (exclude && found || !exclude && !found) return false; // Xor operation or exclude == found
                }
            }

            if (feffkind != 0) {
                switch (feffkind)
                {
                    case 1: // Potions
                        if (item.IsPoison())
                            return false;
                        break;
                    case 2: // Pure Potions
                        foreach (EffectPair effp in item.Effects)
                            if (effp.first.Base.Flags.hostile)
                                return false;
                        break;
                    case 3: // Have Positives
                        bool all_hostile = true;
                        foreach (EffectPair effp in item.Effects)
                            if (!effp.first.Base.Flags.hostile) {
                                all_hostile = false;
                                break;
                            }
                        if (all_hostile)
                            return false;
                        break;
                    case 4: //  Poisons
                        if (!item.IsPoison())
                            return false;
                        break;
                    case 5: // Pure poisons
                        foreach (EffectPair effp in item.Effects)
                            if (!effp.first.Base.Flags.hostile)
                                return false;
                        break;
                    case 6:
                        bool all_positive = true;
                        foreach (EffectPair effp in item.Effects)
                            if (effp.first.Base.Flags.hostile)
                            {
                                all_positive = false;
                                break;
                            }
                        if (all_positive)
                            return false;
                        break;
                    default:
                        return false;
                }
            }

            if (fingcount != 0) {
                if (item.Ingredients.Count != 1 + fingcount) return false;
            }

            switch (feffcountop) { 
                case 0:
                    break;
                case 1: // = 
                    if (item.EffectCount != feffcountval)
                        return false;
                    break;
                case 2: // <
                    if (item.EffectCount > feffcountval)
                        return false;
                    break;
                case 3: // >
                    if (item.EffectCount < feffcountval)
                        return false;
                    break;
                default:
                    return false;
            }

            switch (fvalueop)
            {
                case 0: // any
                    break;
                case 1: // = 
                    if (item.Value != fvalue)
                        return false;
                    break;
                case 2: // <
                    if (item.Value > fvalue)
                        return false;
                    break;
                case 3: // >
                    if (item.Value < fvalue)
                        return false;
                    break;
                default:
                    return false;
            }

            return true;
        }
    }

    class RecipeRow : DataGridViewRow
    {

        public Recipe Recipe { get; set; }

        public override object Clone()
        {
            var clonedRow = base.Clone() as RecipeRow;
            clonedRow.Recipe = this.Recipe;
            return clonedRow;
        }
    }

    // Main database is static:
    static public class MainDB {
        public static SQLiteConnection db = null;
        public static TableQuery<EffectName> tblEffectName = null;
        public static TableQuery<Ingredient> tblIngredient = null;
        public static TableQuery<IngrEffect> tblIngrEffect = null;
        public static TableQuery<EffectGroup> tblEffectGroup = null;
        public static Player player = null;

        public static void openDB() {
            db = new SQLiteConnection("ingdata.db");
            tblEffectName = db.Table<EffectName>();
            tblIngredient = db.Table<Ingredient>();
            tblIngrEffect = db.Table<IngrEffect>();
            tblEffectGroup = db.Table<EffectGroup>();
            player = db.Table<Player>().Where(p => p.Id == 1).First();
            
        }

        public static void closeDB() {
            if (object.ReferenceEquals(db, null)) return;
            // Update player data:
            db.Update(player);
            db.Commit();

            db.Close();
            
            tblEffectName = null;
            tblIngredient = null;
            tblIngrEffect = null;
            tblEffectGroup = null;

            db = null;
        }
    }

    public static class Consts {
        public static double ingredientMult = 4.0;
        public static double alchemySkillFactor = 1.1;
        public static List<int> RestoreSpells { get { return getRestoreSpells(); } }
        private static List<int> restoreSpells = null;
        private static List<int> getRestoreSpells() {
            if (object.ReferenceEquals(null, restoreSpells)) {
                restoreSpells = new List<int>();
                restoreSpells.Add(MainDB.tblEffectName.Where(en => en.EffName == "RestoreHealth").First().Id);
                restoreSpells.Add(MainDB.tblEffectName.Where(en => en.EffName == "RestoreMagicka").First().Id);
                restoreSpells.Add(MainDB.tblEffectName.Where(en => en.EffName == "RestoreStamina").First().Id);
            }
            return restoreSpells;
        }
    }

    // Extend RichBox
    public static class RichTextBoxExtensions
    {
        public static void AppendText(this RichTextBox box, string text, Color color, FontStyle style)
        {
            box.SelectionStart = box.TextLength;
            box.SelectionLength = 0;

            Font oldFont = box.SelectionFont;
            box.SelectionFont = new Font(box.Font, style);
            box.SelectionColor = color;
            box.AppendText(text);
            box.SelectionColor = box.ForeColor;
            box.SelectionFont = oldFont;
        }
    }


    // All controls redraw suspend, resume
    public static class ControlHelper
    {
        #region Redraw Suspend/Resume
        [DllImport("user32.dll", EntryPoint = "SendMessageA", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
        private const int WM_SETREDRAW = 0xB;

        public static void SuspendDrawing(this Control target)
        {
            SendMessage(target.Handle, WM_SETREDRAW, 0, 0);
        }

        public static void ResumeDrawing(this Control target) { ResumeDrawing(target, true); }
        public static void ResumeDrawing(this Control target, bool redraw)
        {
            SendMessage(target.Handle, WM_SETREDRAW, 1, 0);

            if (redraw)
            {
                target.Refresh();
            }
        }
        #endregion
    }

    public class EffectFlags
    {
        #pragma warning disable 414
        public bool no_magnitude = false;
        public bool no_duration = false;
        public bool power_affects_magnitude = false;
        public bool power_affects_duration = false;
        public bool hostile = false;
        public bool recover = false;
        public bool detrimental = false;
        public bool no_area = false;
        public bool fx_persist = false;
        public bool dispel_with_keywords = false;
        #pragma warning restore 414

        public EffectFlags(string aFlags)
        {
            string[] lFlags = Regex.Split(aFlags, ", ");
            foreach (string sFlag in lFlags)
            {
                switch (sFlag.ToLower()) {
                    case "no magnitude":
                        no_magnitude = true;
                        break;
                    case "no duration":
                        no_duration = true;
                        break;
                    case "power affects magnitude":
                        power_affects_magnitude = true;
                        break;
                    case "power affects duration":
                        power_affects_duration = true;
                        break;
                    case "hostile":
                        hostile = true;
                        break;
                    case "recover":
                        recover = true;
                        break;
                    case "detrimental":
                        detrimental = true;
                        break;
                    case "no area":
                        no_area = true;
                        break;
                    case "fx persist":
                        fx_persist = true;
                        break;
                    case "dispel with keywords":
                        dispel_with_keywords = true;
                        break;
                    default:
                        throw new Exception("Flag '"+sFlag+"' not found!");
                }
            }
        }
    }

    public class Power
    {
        public int mag = 0;
        public int dur = 0;
        public int cost = 0;
        IngrEffect eff = null;
        bool making_poison = false;

        int AlchemistPerk() {
            if (MainDB.player.AlchemicalLoreLevel > 1) return 50;
            else if (MainDB.player.AlchemicalLoreLevel > 0) return 25;
            else return 0;
        }

        int PhysicianPerk() {
            if (MainDB.player.ImprovedElixirs == 1 && Consts.RestoreSpells.Contains(eff.EffNameID)) return 25;
            else return 0;
        }

        int BenefactorPerk() {
            if (MainDB.player.ImprovedElixirs == 1 && !making_poison && !eff.Base.Flags.hostile)
                return 25;
            else
                return 0;
        }

        int PoisonerPerk() {
            if (MainDB.player.ImprovedPoisons == 1 && making_poison && eff.Base.Flags.hostile)
                return 25;
            else
                return 0;
        }

        int PurificationPerk() {
            if (MainDB.player.Purification == 1)
            {
                if (making_poison && eff.Base.Flags.hostile)
                    return 80;
                else if (Consts.RestoreSpells.Contains(eff.EffNameID)) 
                    return 170;
                else return 80;
            }
            else return 0;
        }

        double PowerFactor() {
            int alchemistPerk = AlchemistPerk();
            int physicianPerk = PhysicianPerk();
            int benefactorPerk = BenefactorPerk();
            int poisonerPerk = PoisonerPerk();
            int purificationPerk = PurificationPerk();
            return Consts.ingredientMult * (1 + (Consts.alchemySkillFactor - 1) * MainDB.player.AlchemyLevel / 100.0) * 
                (1.0 + MainDB.player.FortifyAlchemy / 100.0) * 
                (1.0 + alchemistPerk / 100.0) * 
                (1.0 + physicianPerk / 100.0) * 
                (1.0 + benefactorPerk / 100.0 + poisonerPerk / 100.0) * 
                (1.0 + purificationPerk / 100.0);
        }

        private void CalcMagnitude() {
            // Find better way to mag adjust
            double mag_adjust = 1.0;
            if (eff.Base.EffectGroup == 3) mag_adjust = 0.5;
            
            double powerFactor = 1.0;
            if (MainDB.player.AlchemicalLoreLevel > 0)
                powerFactor = PowerFactor() * mag_adjust;
            else
                powerFactor = 0.0;
            
            double magnitude = eff.Magnitude;

            if (eff.Base.Flags.no_magnitude)
                magnitude = 0.0;
            double magnitudeFactor = 1.0;
            if (eff.Base.Flags.power_affects_magnitude)
                magnitudeFactor = powerFactor;

            magnitude = Math.Round(magnitude * magnitudeFactor);
            mag = (int)magnitude;

            double duration = eff.Duration;
            if (eff.Base.Flags.no_duration)
                duration = 0.0;
            double durationFactor = 1.0;
            if (eff.Base.Flags.power_affects_duration)
                durationFactor = powerFactor;
               
            duration = Math.Round(duration * durationFactor);
            dur = (int)duration;


            magnitudeFactor = 1.0;
            if (magnitude > 0)
                magnitudeFactor = magnitude;

            durationFactor = 1.0;
            if (duration > 0)
                durationFactor = duration / 10.0;
            
            cost = (int)Math.Floor(eff.Base.BaseCost * Math.Pow(magnitudeFactor * durationFactor, 1.1));
        }

        public Power(IngrEffect eff) : this(eff, eff.Base.Flags.hostile) { // ToDo: check if poinon, non relating to flags
        }

        public Power(IngrEffect eff, bool making_poison) {
            this.eff = eff;
            this.making_poison = making_poison;
            this.CalcMagnitude();
        }

        public string getDescription()
        {
            return eff.Base.EffectDescription.Replace("{mag}", mag.ToString()).Replace("{dur}", dur.ToString());
        }

    }



    // Entities:
    /*
    public class IngredientEl {
        public Ingredient IData;
        List<IngrEffect> Effects = null; 
        public IngredientEl(Ingredient aIData) {
            this.IData = aIData;
            Effects = getEffects();
        }

        public List<IngrEffect> getEffects() {
            if (object.ReferenceEquals(null, Effects))
                Effects = MainDB.tblIngrEffect.Where(ieff => ieff.IngrID == IData.Id).Cast<IngrEffect>().ToList();
            return Effects;
        }

        // Default comparer for Part type. 
        public int CompareTo(IngredientEl compareIngr)
        {
            // A null value means that this object is greater. 
            if (compareIngr == null)
                return 1;

            else
                return this.IData.Id.CompareTo(compareIngr.IData.Id);        
        }

        public override int GetHashCode()
        {
            return this.IData.Id;
        }

        public bool Equals(IngredientEl other)
        {
            if (other == null) return false;
            return (this.IData.Id.Equals(other.IData.Id));
        }
    }
    */
    /*
    public class IngrEffectEl {
        public IngrEffect IEffData;
        public IngrEffectEl(IngrEffect aIEffData) {
            this.IEffData = aIEffData;
        }
    }    
    //*/

    public class Recipe {
        public List<Ingredient> Ingredients { get; set; }
        // private List<IngrEffect> effects = null;
        public List<EffectPair> Effects { get { return effects; } }


        public string Name { get { return name; } }

        private string ingrstr = "";
        public string IngredientStr { 
            get { 
                if (ingrstr == "") ingrstr = String.Join(" + ", Ingredients); 
                return ingrstr; 
            } 
        }

        public int IngredientCount { get { return Ingredients.Count;  } }

        private string effectstr = "";
        public string EffectStr { 
            get { 
                if (effectstr == "") effectstr = String.Join(" ", Effects);
                return effectstr;
            } 
        }

        public int EffectCount { get { return effects.Count; } }

        public int Value { get { return value; } }

        private int value = 0;

        private string name = "";

        private List<EffectPair> effects = new List<EffectPair>();

        private List<int> ingrUsed = new List<int>();

        public Recipe(List<Ingredient> aIngredients) {
            Ingredients = aIngredients;
            // Calculate common effects:
            MixPotion();
        }

        private EffectPair GetBestEffect(int effid) {
            IngrEffect first = null; IngrEffect second = null;
            foreach (Ingredient ing in Ingredients) {
                foreach (IngrEffect eff in ing.Effects) {
                    if (eff.EffNameID == effid) { 
                        // ToDo: This needs changing to eff.prevaluation
                        if (object.ReferenceEquals(null, first) || eff.Pow.cost > first.Pow.cost) {
                            second = first; first = eff;
                        }
                        else if (object.ReferenceEquals(null, second) || eff.Pow.cost > second.Pow.cost) {
                            second = eff;
                        }
                    }
                }
            }            
            return new EffectPair(first, second);
        }

        public bool IsPoison() {
            if (effects.Count() > 0)
                return effects.First().first.Base.Flags.hostile;
            else return false;
        }

        public void ReMixPotion() {
            effectstr = "";
            ingrstr = "";
            MixPotion();
        }

        private void MixPotion() {
            // Get ingredient effect ID's
            List<List<int>> efflists = new List<List<int>>();
            foreach (Ingredient ingr in Ingredients) {
                efflists.Add(ingr.EffIds);
            }
            var efflistpairs = new Combinations<List<int>>(efflists, 2);
            var allids = new List<int>();
            foreach (List<List<int>> efflp in efflistpairs) {
                IEnumerable<int> matches = efflp[0].Intersect<int>(efflp[1]);
                allids = allids.Union<int>(matches).ToList();
            }
            effects.Clear();
            ingrUsed.Clear();
            foreach (int effid in allids) {
                EffectPair ep = GetBestEffect(effid);
                effects.Add(ep);
                ingrUsed = ingrUsed.Union<int>( new List<int>() { ep.first.IngrID, ep.second.IngrID } ).ToList();
           } 

            // Sort effects by cost, descending (effp2, effp1 swapped)
            effects.Sort((effp1, effp2) => effp2.first.Pow.cost.CompareTo(effp1.first.Pow.cost));

            if (MainDB.player.Purification > 0) {
                if (IsPoison())
                {
                    effects = effects.FindAll(eff => eff.first.Base.Flags.hostile);
                }
                else {
                    effects = effects.FindAll(eff => !eff.first.Base.Flags.hostile);
                }
            }
            
            // Calculate powers for each effect, and total value of potion:
            value = 0;
            foreach (EffectPair effp in effects) {
                effp.pow = new Power(effp.first, IsPoison());
                value += effp.pow.cost;
            }

            if (effects.Count() > 0) {
                EffectPair eff = effects.First();
                if (eff.first.Base.Flags.hostile)
                    name = "Poison of " + eff.first.Base.ReqName;
                else
                    name = "Potion of " + eff.first.Base.ReqName;
            }
        }

        public bool IsDud() {
            return ingrUsed.Count() < Ingredients.Count();
        }

        public override string ToString() {            
            if (IsDud()) {
                return String.Join(" + ", Ingredients) + " : DUD";
            } else return String.Join(" + ", Ingredients) + " : " + String.Join(" ", effects);

            // foreach (Ingredient ingr in Ingredients)
               // ingr.Name
        }
    }

    public class EffectPair {
        public IngrEffect first;
        public IngrEffect second;
        public Power pow = null;
        public EffectPair(IngrEffect aFirst, IngrEffect aSecond) {
            first = aFirst;
            second = aSecond;
        }

        public override string ToString()
        {
            // return String.Format("E({0}, {1})", first, second);
            // return String.Format(first.Base.EffectDescription, )
            return pow.getDescription();
        }
    }
  
    // Database item interfaces:
    [Table("player")]
    public class Player {
        [PrimaryKey, AutoIncrement, Column("ID")]
        public int Id { get; set; }
        public int AlchemyLevel { get; set; }
        public int FortifyAlchemy { get; set; }
        public int AlchemicalLoreLevel { get; set; }
        public int ImprovedElixirs { get; set; }
        public int ImprovedPoisons { get; set; }
        public int Purification { get; set; }
    }

    public class EffectGroup {
        [PrimaryKey, AutoIncrement, Column("ID")]
        public int Id { get; set; }
        public string GroupName { get; set; }
    }

    public class EffectName {
        [PrimaryKey, AutoIncrement, Column("ID")]
        public int Id { get; set; } 
        public string ReqName { get; set; }
        public string EffName { get; set; }
        public int EffectGroup { get; set; }
        public float BaseCost { get; set; }
        [Column("Flags")]
        public string FlagStr { get; set; }
        public string EffectDescription { get; set; }
        [Ignore]
        public EffectGroup Group { get { return getGroup(); } }
        private EffectGroup group = null;
        private EffectGroup getGroup()
        {
            if (object.ReferenceEquals(null, group)) {
                group = MainDB.tblEffectGroup.Where(egrp => egrp.Id == this.EffectGroup).First();
            }
            return group;
        }
        [Ignore]
        public EffectFlags Flags { get { return getFlags(); } }
        private EffectFlags flags = null;
        private EffectFlags getFlags() { 
            if (object.ReferenceEquals(null, flags)) {
                flags = new EffectFlags(FlagStr);
            }
            return flags;
        }
    }

    public class IngrEffect {
        [PrimaryKey, AutoIncrement, Column("ID")]
        public int Id { get; set; } 
        public int IngrID { get; set; }
        public int EffNameID { get; set; }
        public float Magnitude { get; set; }
        public float Duration { get; set; }
        public int Priority { get; set; }
        public float MagAdjust { get; set; }
        public float DurAdjust { get; set; }
        public float CostAdjust { get; set; }
        private EffectName effBase = null;
        [Ignore]
        public EffectName Base { get { return getBase(); } }
        [Ignore]
        public Ingredient Ingr { get; set; }
        private EffectName getBase() {
            if (object.ReferenceEquals(null, effBase))
                effBase = MainDB.tblEffectName.Where(iename => iename.Id == EffNameID).First();
            return effBase;
        }

        public Power Pow { get { return getPow(); } }

        private Power pow = null;
        private Power getPow() {
            if (object.ReferenceEquals(null, pow)) 
                UpdatePow();
            return pow;
        }

        public void UpdatePow() {
            pow = new Power(this);
        }

        public override string ToString()
        {
            return String.Format("{0}. {1} ", Id, Base.ReqName);
        }
    }

    public class Ingredient
    {
        [PrimaryKey, AutoIncrement, Column("ID")]
        public int Id { get; set; }
        public int Checked { get; set; }
        public string Name { get; set; }
        public int Value { get; set; }
        [Ignore]
        public List<IngrEffect> Effects { get { return getEffects(); } } 
        private List<IngrEffect> effects = null;
        private List<IngrEffect> getEffects() {
            if (object.ReferenceEquals(null, effects))
                effects = MainDB.tblIngrEffect.Where(ieff => ieff.IngrID == Id).Cast<IngrEffect>().ToList();

            foreach (IngrEffect effect in effects) {
                effect.Ingr = this;
            }
            return effects;
        }

        [Ignore]
        public List<int> EffIds { get { return getEffIdList(); } }
        private List<int> effIds = null;
        private List<int> getEffIdList() { 
            if (object.ReferenceEquals(null, effIds)) {
                effIds = new List<int>();
                foreach (IngrEffect eff in Effects)
                    effIds.Add(eff.EffNameID);
            }
            return effIds;
        }

        public override string ToString()
        {
            return Name;
        }        
    }

}
